/*
 * Copyright (C) 2003,2004 Christopher J. Stawarz <cstawarz@cfa.harvard.edu>
 *
 * This file is part of SLglade.
 *
 * SLglade is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * SLglade is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with SLglade; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#include <string.h>



/****************************************************************
 *
 * Internal functions and data structures
 *
 ****************************************************************/


/*
 * Finalizer to be called when GladeXML objects go out of scope
 */
static void slglade_xml_destroy(GladeXML *xml) {
   g_object_unref(G_OBJECT(xml));
}


/*
 * Instead of writing our own closure mechanism for connecting
 * signal handlers, we use the one from SLgtk.  These two
 * functions come from slglib.c.
 *
 * This does make for a pretty ugly dependency on the internals
 * of SLgtk, but oh well ...
 */
extern int extract_slang_args(unsigned int nargs, SLang_Any_Type ***pargs);
extern GClosure* slg_closure_new(SLang_Name_Type *function, SLang_Any_Type **args,
                                 unsigned int nargs, SLang_Any_Type *swap_data);


/*
 * Data structure for passing handler-function info
 */
typedef struct {
   SLang_Name_Type *function;	/* S-Lang function to handle signal */
   SLang_Any_Type **args;	/* Function args passed in from S-Lang scope */
   unsigned int  nargs;		/* Number of args */
} signal_connect_data;


/*
 * GladeXMLConnectFunc for use with glade_xml_signal_connect_full()
 * and glade_xml_signal_autoconnect_full().  (I figured out how to
 * make this work by studying examples in libglade and SLgtk.)
 */
static void
signal_connect_connect_func(const gchar *handler_name, GObject *object,
			    const gchar *signal_name, const gchar *signal_data,
			    GObject *connect_object, gboolean after,
			    gpointer user_data)
{
   /* glade_xml_signal_connect_full() and glade_xml_signal_autoconnect_full()
    * both pass NULL for signal_data, so there's no need to worry about it. */

   char *handler;
   signal_connect_data *data = (signal_connect_data *)user_data;
   SLang_Name_Type *function = NULL;
   SLang_Any_Type *conn_obj;

   /*
    * Retrieve the S-Lang function to connect as signal handler
    */

   if (data->function) {
      function = data->function;
   } else {
      /* Make a copy of handler_name so that the compiler doesn't complain
       * that we're passing a const pointer to a function (SLang_get_function)
       * that takes a non-const argument */
      handler = SLmalloc((strlen(handler_name) + 1) * sizeof(char));
      strcpy(handler,handler_name);

      function = SLang_get_function(handler);

      if (function == NULL)
         SLang_vmessage("SLglade: WARNING: cannot find function matching name \"%s\"",
	   handler);

      SLfree(handler);
   }

   /*
    * Connect the handler
    */

   if (function == NULL) {
      SLang_vmessage(
	"SLglade: WARNING: cannot connect handler function to signal \"%s\" on widget \"%s\"",
	signal_name,glade_get_widget_name((GtkWidget *)object));
   } else {
      if (connect_object == NULL) {
         /* No connect_object was specified, so when called, the
	  * handler will be passed the object that generated the signal */

         g_signal_connect_closure(object, signal_name,
           slg_closure_new(function,data->args,data->nargs,NULL),after);
      } else {
         /* connect_object specified, so the handler will be passed this
	  * object instead of the one that generated the signal.
	  * (This occurs when an object name is specified with the
	  * signal handler in Glade.  The effect is like that of
	  * g_signal_connect_swapped().) */

         /* I guessed that connect_object should be the swap_data in the
	  * slGClosure based on an examination of the functions
	  * pyg_closure_new() in pygtype.c and pygobject_connect_object()
	  * in pygobject.c of the pygtk-2.0.0 distribution and the description
	  * of g_signal_connect_swapped() in the GTK+ API docs.  This seems to
	  * work, so I think I'm right. */

	 /* This looks a bit hackish (pushing the GObject onto the stack and
	  * then immediately popping it off), but it seems like the easiest way
	  * to typecast from GObject to Any_Type. */

         /* Probably ought to check for failure here, but I don't know how
	  * this could fail */
         GObject_Type_push(connect_object, GObject_Type);
	 SLang_pop_anytype(&conn_obj);

         g_signal_connect_closure(object, signal_name,
           slg_closure_new(function,data->args,data->nargs,conn_obj),
	   (after ? G_CONNECT_AFTER : 0) | G_CONNECT_SWAPPED);
      }
   }
}



/****************************************************************
 *
 * Module functions
 *
 ****************************************************************/


/*
 * glade_require()
 *
 * I don't know if this would ever be useful from S-Lang scope,
 * but we'll wrap it anyway.
 */
static void slglade_require (void)
{
   char* library;

   if (SLang_Num_Function_Args != 1) {
      SLang_verror (SL_USAGE_ERROR, "Usage: glade_require(String);");
      return;
   }

   if (SLANG_STRING_TYPE_pop(&library, NULL) == -1 ) {
      SLang_verror (SL_INTRINSIC_ERROR,
        "Unable to validate arguments to: glade_require");
   } else {
      glade_require(library);
   }

   SLang_free_slstring(library);
}


/*
 * glade_xml_new()
 *
 * We search for the input file first in the current directory,
 * then along SLGLADE_XML_PATH, and finally along S-Lang's load
 * path.
 */
static void slglade_xml_new (void)
{
   GladeXML* xml;
   char* fname;
   char* root = NULL;
   char* domain = NULL;

   char* file = NULL;		/* Full path to file */
   char* sl_path = NULL;	/* S-Lang load path */

   int status = 0;		/* Error status */

   switch (SLang_Num_Function_Args) {
      case 3:
        status += pop_obj_or_null(SLANG_STRING_TYPE,(void**)&domain);
      case 2:
	status += pop_obj_or_null(SLANG_STRING_TYPE,(void**)&root);
      case 1:
	status += SLANG_STRING_TYPE_pop(&fname, NULL);
	break;
      default:
	SLang_verror (SL_USAGE_ERROR,
	   "Usage: GladeXML = glade_xml_new(String[,String[,String]]);");
	return;
   }

   if (0 != status) {
      SLang_verror (SL_INTRINSIC_ERROR,
	"Unable to validate arguments to: glade_xml_new");
   } else {
      /*
       * Search for XML file using SLpath_find_file_in_path()
       *
       * Note that it's safe to pass NULL as the path argument to this
       * function (it will return NULL)
       */

      /* First, look in current directory */
      file = SLpath_find_file_in_path(".", fname);

      /* Second, search along SLGLADE_XML_PATH */
      if (NULL == file)
         file = SLpath_find_file_in_path(getenv("SLGLADE_XML_PATH"), fname);

      /* Third, search along S-Lang load path */
      if (NULL == file) {
         sl_path = SLpath_get_load_path();
         file = SLpath_find_file_in_path(sl_path, fname);
	 SLang_free_slstring(sl_path);
      }

      /*
       * Load XML file
       */

      if (NULL != file) {
         /* Found file with SLpath_find_file_in_path(), so load it */
         xml = glade_xml_new(file,root,domain);
         SLfree(file);
      } else {
         /* Didn't find file, so pass the input name to libglade;
	  * presumably, it will just fail, but this saves us the trouble of
	  * printing an error message */
         xml = glade_xml_new(fname,root,domain);
      }

      /* Push the GladeXML object onto the stack */
      GladeXML_Type_push(xml, GladeXML_Type);
   }

   SLang_free_slstring(fname);
   SLang_free_slstring(root);
   SLang_free_slstring(domain);
}


/*
 * glade_xml_new_from_buffer()
 *
 * Another one that probably won't be useful from S-Lang
 * scope, but let's wrap it anyway.
 */
static void slglade_xml_new_from_buffer (void)
{
   GladeXML* xml;
   char* buffer;
   char* root = NULL;
   char* domain = NULL;

   int status = 0;  /* Error status */

   switch (SLang_Num_Function_Args) {
      case 3:
        status += pop_obj_or_null(SLANG_STRING_TYPE,(void**)&domain);
      case 2:
	status += pop_obj_or_null(SLANG_STRING_TYPE,(void**)&root);
      case 1:
	status += SLANG_STRING_TYPE_pop(&buffer, NULL);
	break;
      default:
	SLang_verror (SL_USAGE_ERROR,
	  "Usage: GladeXML = glade_xml_new_from_buffer(String[,String[,String]]);");
	return;
   }

   if (0 != status) {
      SLang_verror (SL_INTRINSIC_ERROR,
        "Unable to validate arguments to: glade_xml_new_from_buffer");
   } else {
      xml = glade_xml_new_from_buffer(buffer, (int)strlen(buffer), root, domain);
      GladeXML_Type_push(xml, GladeXML_Type);
   }

   SLang_free_slstring(buffer);
   SLang_free_slstring(root);
   SLang_free_slstring(domain);
}


/*
 * glade_xml_signal_connect()
 *
 * The mechanics of this are modeled off one of the
 * signal-connection functions in SLgtk (I forget which
 * one).
 */
static void slglade_xml_signal_connect(void)
{
   Slirp_Opaque *xml_self;
   char *handlername = NULL;
   SLang_Ref_Type *function_ref = NULL;
   signal_connect_data data;

   SLang_MMT_Type *mmt = NULL;
   SLang_Name_Type *function;
   SLang_Any_Type **args;
   unsigned int nargs;

   if (SLang_Num_Function_Args < 3) {
      SLang_verror (SL_USAGE_ERROR,
        "Usage: glade_xml_signal_connect(GladeXML,String,function_ref[,data,...]);");
      return;
   }

   nargs = SLang_Num_Function_Args - 3;

   if (extract_slang_args(nargs,&args) == -1 ||
         SLang_pop_ref(&function_ref) == -1 ||
         SLang_pop_slstring(&handlername) == -1 ||
	 SLang_pop_opaque(GladeXML_Type, &xml_self, &mmt) == -1 ||
         NULL == (function = SLang_get_fun_from_ref(function_ref))) {
      SLang_verror (SL_INTRINSIC_ERROR,
        "Unable to validate arguments to: glade_xml_signal_connect");
   } else {
      data.function = function;
      data.args = args;
      data.nargs = nargs;

      glade_xml_signal_connect_full(SAFE_DEREF_OPAQUE(xml_self), handlername,
        signal_connect_connect_func, &data);
   }

   SLang_free_mmt(mmt);
   SLang_free_slstring(handlername);
   SLang_free_ref(function_ref);
}


/*
 * glade_xml_signal_autoconnect()
 */
static void slglade_xml_signal_autoconnect(void)
{
   Slirp_Opaque *xml_self;
   signal_connect_data data;

   SLang_MMT_Type *mmt = NULL;

   if (SLang_Num_Function_Args != 1) {
      SLang_verror (SL_USAGE_ERROR,
        "Usage: glade_xml_signal_autoconnect(GladeXML);");
      return;
   }

   if (SLang_pop_opaque(GladeXML_Type, &xml_self, &mmt) == -1) {
      SLang_verror (SL_INTRINSIC_ERROR,
        "Unable to validate arguments to: glade_xml_signal_autoconnect");
      return;
   }

   data.function = NULL;
   data.args = NULL;
   data.nargs = 0;

   glade_xml_signal_autoconnect_full(SAFE_DEREF_OPAQUE(xml_self),
     signal_connect_connect_func, &data);

   SLang_free_mmt(mmt);
}


/*
 * glade_xml_get_widget()
 */
static void slglade_xml_get_widget (void)
{
   GtkWidget* wdg;
   Slirp_Opaque* xml_opq = NULL;
   SLang_MMT_Type *xml_mmt = NULL;
   char* name;

   if (SLang_Num_Function_Args != 2) {
	SLang_verror (SL_USAGE_ERROR,
	   "Usage: GtkWidget = glade_xml_get_widget(GladeXML,String);");
	return;
   }

   if (SLANG_STRING_TYPE_pop(&name, NULL) == -1 ||
         SLang_pop_opaque(GladeXML_Type, &xml_opq, &xml_mmt) == -1 ) {
      SLang_verror (SL_INTRINSIC_ERROR,
	   "Unable to validate arguments to: glade_xml_get_widget");
   } else {
      wdg = glade_xml_get_widget(SAFE_DEREF_OPAQUE(xml_opq), name);

      /* Since this function creates a new GtkWidget object in S-Lang scope, we
       * need to increase the reference count to the C-scope GtkWidget object */
      /* Actually, since SLgtk appears not to assign a finalizer to GtkWidgets
       * (meaning there won't be a corresponding call to gtk_widget_unref()),
       * maybe I shouldn't do this */
      /*gtk_widget_ref(wdg);*/

      (void) GtkWidget_Type_push(wdg, GtkWidget_Type);
   }

   SLang_free_mmt(xml_mmt);
   SLang_free_slstring(name);
}


/*
 * glade_xml_get_widget_prefix()
 *
 * Since a GList wouldn't be very useful in S-Lang scope,
 * we return an array of GtkWidget's instead.
 */
static void slglade_xml_get_widget_prefix (void)
{
   Slirp_Opaque* xml_opq = NULL;
   SLang_MMT_Type *xml_mmt = NULL;
   char* name;

   GList* list;
   SLang_Array_Type *rtn_array;
   SLang_MMT_Type *wdg_mmt;
   int array_count;

   if (SLang_Num_Function_Args != 2) {
	SLang_verror (SL_USAGE_ERROR,
	   "Usage: GtkWidget[] = glade_xml_get_widget_prefix(GladeXML,String);");
	return;
   }

   if (pop_obj_or_null(SLANG_STRING_TYPE,(void**)&name) == -1 ||
	 SLang_pop_opaque(GladeXML_Type, &xml_opq, &xml_mmt) == -1 ) {
      SLang_verror (SL_INTRINSIC_ERROR,
        "Unable to validate arguments to: glade_xml_get_widget_prefix");
      SLang_free_mmt(xml_mmt);
      SLang_free_slstring(name);
      return;
   }

   /* Treat a NULL name as the empty string */
   if (name == NULL)  name = SLang_create_slstring("");

   list = glade_xml_get_widget_prefix(SAFE_DEREF_OPAQUE(xml_opq), name);

   SLang_free_mmt(xml_mmt);
   SLang_free_slstring(name);

   /* No matching widgets, so return NULL */
   if (list == NULL) {
      SLang_push_null();
      return;
   }

   /*
    * Found some matching widgets, so make the array to return
    */

   array_count = g_list_length(list);
   rtn_array = SLang_create_array(GtkWidget_Type,0,NULL,&array_count,1);

   if (rtn_array == NULL)  return;

   array_count = 0;

   while (list != NULL) {
      wdg_mmt = create_opaque_mmt(GtkWidget_Type, (GtkWidget *)list->data, 0);

      if (-1 == SLang_set_array_element(rtn_array, &array_count, &wdg_mmt)) {
         SLang_free_array(rtn_array);
         return;
      }

      /* We should NOT call SLang_free_mmt() here -- it causes a seg fault */

      list = list->next;
      array_count++;
   }

   (void) SLang_push_array (rtn_array, 1);
}


/*
 * glade_get_widget_name()
 */
static void slglade_get_widget_name (void)
{
   const char* name;
   Slirp_Opaque* wdg_opq = NULL;
   SLang_MMT_Type *wdg_mmt = NULL;

   if (SLang_Num_Function_Args != 1) {
	SLang_verror (SL_USAGE_ERROR,
	   "Usage: String = glade_get_widget_name(GtkWidget);");
	return;
   }

   if (SLang_pop_opaque(GtkWidget_Type, &wdg_opq, &wdg_mmt) == -1 ) {
      SLang_verror (SL_INTRINSIC_ERROR,
        "Unable to validate arguments to: glade_get_widget_name");
   } else {
      name = glade_get_widget_name(SAFE_DEREF_OPAQUE(wdg_opq));
      (void) SLANG_STRING_TYPE_push((char *)name, NULL);
   }

   SLang_free_mmt(wdg_mmt);
}


/*
 * glade_get_widget_tree()
 */
static void slglade_get_widget_tree (void)
{
   GladeXML* xml;
   Slirp_Opaque* wdg_opq = NULL;
   SLang_MMT_Type *wdg_mmt = NULL;

   if (SLang_Num_Function_Args != 1) {
	SLang_verror (SL_USAGE_ERROR,
	   "Usage: GladeXML = glade_get_widget_tree(GtkWidget);");
	return;
   }

   if (SLang_pop_opaque(GtkWidget_Type, &wdg_opq, &wdg_mmt) == -1 ) {
      SLang_verror (SL_INTRINSIC_ERROR,
        "Unable to validate arguments to: glade_get_widget_tree");
   } else {
      xml = glade_get_widget_tree(SAFE_DEREF_OPAQUE(wdg_opq));

      /* Since this function creates a new GladeXML object in S-Lang scope, we
       * need to increase the reference count to the C-scope GladeXML object */
      g_object_ref(G_OBJECT(xml));

      (void) GladeXML_Type_push(xml, GladeXML_Type);
   }

   SLang_free_mmt(wdg_mmt);
}


